##  Cate - Copyright (C) 2006 - Marco Bubke
##  This program is free software; you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation; either version 2, or (at your option)
##  any later version.
##  
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.

from OpenGL.GL.ARB.shader_objects import *
from OpenGL.GL.ARB.fragment_shader import *
from OpenGL.GL.ARB.vertex_shader import *
from OpenGL.GL import *
import sys
from OpenGL.error import GLError
from PyQt4 import QtCore
from elementtree.ElementTree import Element, SubElement
from opengl_object import OpenGLObject


__all__ = ['Program', 'FragmentShader', 'VertexShader']

class AttributesModel(QtCore.QAbstractItemModel):
  def __init__(self, parent=None):
    QtCore.QAbstractItemModel.__init__(self)
    self.itemList = []
  
  def rowCount(self, parent):
    return len(self.itemList)

  def columnCount(self, parent):
    return 4
    
  def data(self, index, role):
    if index.isValid() and role == QtCore.Qt.DisplayRole:
      try:
        return  QtCore.QVariant(self.itemList[index.row()][index.column()])
      except IndexError:
        return QtCore.QVariant()
    else:
      return  QtCore.QVariant()
  
  def headerData(self, section, orientation, role = QtCore.Qt.DisplayRole):
    if role != QtCore.Qt.DisplayRole:
      return QtCore.QVariant()
    
    if orientation == QtCore.Qt.Horizontal:
      if section == 0:
        return QtCore.QVariant('Name')
      elif section == 1:
        return QtCore.QVariant('Type')
      elif section == 2:
        return QtCore.QVariant('Size')
      elif section == 3:
        return QtCore.QVariant('Index')
      else:
        return QtCore.QVariant()
    else:
      return QtCore.QVariant(QtCore.QString("Attribute %1").arg(section))

  def flags(self, index):
    if not index.isValid():
      return QtCore.Qt.ItemIsEnabled
    else:
      return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
  
  def index(self, row, column, parent):
    if not parent.isValid():
      return self.createIndex(row, column, None) 
    else:
      return QtCore.QModelIndex()
    
  def parent(self, index):
    return QtCore.QModelIndex()
    
  def hasChildren(self, index):
    if index.isValid():
      return False
    else:
      return True
  
  def setItems(self, newItemList):
    self.itemList = newItemList
##    print newItemList
##    for name, typeString, size, index in newItemList:
##      for nameInList, typeString, size, index in  self.itemList:
##        if nameInList == name:
##          break
##      self.itemList.append((name, typeString, size, index))
    self.emit(QtCore.SIGNAL('layoutChanged()'))


class UniformsModel(AttributesModel):
  def __init__(self, parent=None):
    AttributesModel.__init__(self, parent)

class Program(OpenGLObject):
  def __init__(self, name):
    OpenGLObject.__init__(self, name, 'Program')
    self.handle = glCreateProgramObjectARB()
    self.attachments = {}
    self.attributesModel =  AttributesModel()
    self.uniformsModel =  UniformsModel()
    
  def __delete__(self):
    glDeleteObjectARB(self.handle)
  
  def attach(self, shader):
    if shader.name in self.attachments:
      return
    glAttachObjectARB(self.handle, shader.handle)
    self.attachments[shader.name] = shader
    self.connect(shader, QtCore.SIGNAL("shaderChanged"), self.link)
    self.emit(QtCore.SIGNAL('shaderAttached'), shader)
    self.link()
    
  def detach(self, shader):
    if not shader.name in self.attachments:
      return   
    glDetachObjectARB(self.handle, shader.handle)
    del self.attachments[shader.name]
    self.disconnect(shader, QtCore.SIGNAL("shaderChanged"), self.link)
    self.emit(QtCore.SIGNAL('shaderDetached'), shader)

  def link(self):
    try:
      glLinkProgramARB(self.handle)
    except GLError:
      if sys.exc_value.result == None:
        self.emit(QtCore.SIGNAL("shaderLog"), 
          sys.exc_value.description)
      else:
        raise
    else:
      self.emit(QtCore.SIGNAL("shaderLog"), 
          "program successfull linked")
      self.emit(QtCore.SIGNAL("linkingOk"))
      self.changeUniformsInModel()
      self.changeAttributesInModel()
    
  def use(self):
    glUseProgramObjectARB(self.handle)
    
  def getInfoLog(self):
    return glGetInfoLogARB(self.handle)
    
  def enumToStringType(self, enumType):
    if enumType == GL_FLOAT: 
      return 'Float'
    elif enumType == GL_FLOAT_VEC2_ARB:
      return 'FVec2'
    elif enumType == GL_FLOAT_VEC3_ARB:
      return 'FVec3'    
    elif enumType == GL_FLOAT_VEC4_ARB:
      return 'FVec4'
    elif enumType == GL_INT:
      return 'Int'       
    elif enumType == GL_INT_VEC2_ARB:
      return 'IVec2'
    elif enumType == GL_INT_VEC3_ARB:
      return 'IVec3'    
    elif enumType == GL_INT_VEC4_ARB:
      return 'IVec4'
    elif enumType == GL_BOOL_ARB:
      return 'Bool'       
    elif enumType == GL_BOOL_VEC2_ARB:
      return 'BVec2'
    elif enumType == GL_BOOL_VEC3_ARB:
      return 'BVec3'    
    elif enumType == GL_BOOL_VEC4_ARB:
      return 'BVec4'  
    elif enumType == GL_FLOAT_MAT2_ARB:
      return 'Mat2'       
    elif enumType == GL_FLOAT_MAT3_ARB:
      return 'Mat3'
    elif enumType == GL_FLOAT_MAT4_ARB:
      return 'Mat4'
    elif enumType == GL_SAMPLER_1D_ARB:
      return 'Sampler1D'    
    elif enumType == GL_SAMPLER_2D_ARB:
      return 'Sampler2D'  
    elif enumType == GL_SAMPLER_3D_ARB:
      return 'Sampler3D'       
    elif enumType == GL_SAMPLER_CUBE_ARB:
      return 'SamplerCube'
    elif enumType == GL_SAMPLER_1D_SHADOW_ARB:
      return 'Sampler1DShadow'      
    elif enumType == GL_SAMPLER_2D_SHADOW_ARB:
      return 'Sampler2DShadow'       
    elif enumType == GL_SAMPLER_2D_RECT_ARB:
      return 'Sampler2DRect'
    elif enumType == GL_SAMPLER_2D_RECT_SHADOW_ARB:
      return 'Sampler2DRectShadow'
    else:
      raise TypeError, 'GL type unkown' 
      
  def getAttributeByIndex(self, index):
    return glGetActiveAttribARB(self.handle, index)
          
  def changeItemInModel(self, model, getByIndexFunction, maxItemEnum):
    maxIndex = glGetObjectParameterivARB(self.handle, 
      maxItemEnum)
    items = []
    for index in xrange(maxIndex):
      name, size, glType = getByIndexFunction(index)
      typeString = self.enumToStringType(glType)
      items.append((name, typeString, size, index))
    model.setItems(items)
  
  def changeAttributesInModel(self):
    self.changeItemInModel(self.attributesModel, 
      self.getAttributeByIndex, GL_OBJECT_ACTIVE_ATTRIBUTES_ARB)

  def getUniformByIndex(self, index):
    return glGetActiveUniformARB(self.handle, index)
    
  def changeUniformsInModel(self):
    self.changeItemInModel(self.uniformsModel, 
      self.getUniformByIndex,GL_OBJECT_ACTIVE_UNIFORMS_ARB )
  
class Shader(OpenGLObject):
  def __init__(self, name, type, glType):
    OpenGLObject.__init__(self, name, type)
    self.handle = glCreateShaderObjectARB(glType)


  def __delete__(self):
    glDeleteObjectARB(self.handle)
  
  def setSource(self, source):
    glShaderSourceARB(self.handle, source)
    self.compile()
    self.emit(QtCore.SIGNAL("shaderChanged"))
    self.emit(QtCore.SIGNAL("shaderSourceChanged"), source)
    
  def getSource(self):
    return glGetShaderSourceARB(self.handle)
    
  source = property(fset=setSource, fget=getSource)
  
  def compile(self):
    try:
      glCompileShaderARB(self.handle)
    except GLError:
      if sys.exc_value.result == None:
        self.emit(QtCore.SIGNAL("shaderLog"), 
          sys.exc_value.description)
      else:
        raise
    else:
      self.emit(QtCore.SIGNAL("shaderLog"), 
          "shader successfull compiled")

  def getInfoLog(self):
    return glGetInfoLogARB(self.handle)
    
  def changeXMLByAttributes(self, objects):
    elements = objects.findall(self.type)
    for elem in elements:
      if elem.get('name') == self.name:
        node = elem
        break
    else:
      node = SubElement(objects, self.type)
      node.set('name', self.name)

    source = node.find('source')
    if not source:
      source = SubElement(node, 'source')
    source.text = self.getSource()
    
  def changeAttributesByXML(self, objects):
    elements = objects.findall(self.type)
    for elem in elements:
      if elem.get('name') == name:
        source = node.find('source')
        try:
          node.setSource(source.text)
        except AttributeError:
          raise AttributeError, 'No source node!'
        break
    else:
      raise AttributeError, 'No matching XML node for this object!' 

class FragmentShader(Shader):
  def __init__(self, name, source):
    Shader.__init__(self, name, 'FragmentShader', GL_FRAGMENT_SHADER_ARB)
    self.setSource(source)
    
class VertexShader(Shader):
  def __init__(self, name, source):
    Shader.__init__(self, name, 'VertexShader', GL_VERTEX_SHADER_ARB)   
    self.setSource(source)

  
